El mito: SQLite es para apps móviles y prototipos. La realidad en 2024: miles de aplicaciones productivas — desde Tailscale hasta Fly.io, Expensify, o pequeños SaaS — lo usan como BD principal. La combinación de SQLite + WAL mode + Litestream + (opcionalmente) LiteFS hace posible escalar donde muchos equipos asumen que necesitan PostgreSQL. Este artículo cubre cómo, cuándo, y sus límites reales.
Por qué SQLite es viable serverside
Las asunciones tradicionales sobre SQLite están obsoletas:
- “No escala”: falso. SQLite maneja miles de writes/s y cientos de miles de reads/s en hardware modesto.
- “Concurrent writes no”: cierto históricamente, pero WAL mode mitiga mucho.
- “No hay replicación”: Litestream, LiteFS, rqlite resuelven.
- “No hay backup tool”: Litestream stream a S3 compatible.
Para apps con 1 proceso / 1 instancia, SQLite es espectacularmente productivo.
WAL mode: el cambio fundamental
Write-Ahead Logging, habilitable con PRAGMA journal_mode=WAL:
- Reads concurrentes no bloquean writes.
- Writes concurrentes aún se serializan pero con menos contención.
- Mejor durabilidad vs journal_mode tradicional.
- Checkpointing incremental de WAL a BD.
Casi todo deployment serio de SQLite usa WAL. Es el default razonable.
Optimizaciones productivas
Pragmas recomendados para producción:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL; -- vs FULL: trade-off durabilidad/perf
PRAGMA cache_size = -64000; -- 64MB
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000; -- 5s antes de SQLITE_BUSY
PRAGMA temp_store = MEMORY;
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped
Diferencias de rendimiento 3-10x comparado con defaults.
Litestream: replicación a S3
Litestream streams el WAL a S3 (o compatible) en near-realtime:
# /etc/litestream.yml
dbs:
- path: /data/app.db
replicas:
- type: s3
bucket: my-backups
path: app.db
region: eu-west-1
access-key-id: ${AWS_ACCESS_KEY_ID}
secret-access-key: ${AWS_SECRET_ACCESS_KEY}
Restore trivial:
litestream restore -o /data/app.db s3://my-backups/app.db
Para backup productivo, Litestream hace SQLite casi comparable a Postgres en durabilidad.
LiteFS: replicación multi-nodo
LiteFS (de Fly.io) lleva más lejos: replica completa de SQLite entre nodos con FUSE filesystem.
- Primary + replicas: writes al primary, reads en cualquier nodo.
- Consistencia fuerte con leasing.
- Failover automatizado.
Útil para aplicaciones que requieren alta disponibilidad o globales (Fly.io usa esto para su propio Postgres clustering alternativo).
rqlite: SQLite distribuida
rqlite es otro enfoque: SQLite con Raft para consenso distribuido. Más complicado pero full-cluster.
Para casos de 3+ nodos con failover automático y replicación estricta, rqlite es más robusto que LiteFS.
Casos reales
- Tailscale: coordination server en SQLite + Litestream.
- Expensify: Bedrock (wrapper distribuido) en SQLite, millones de usuarios.
- PocketBase: BaaS completo basado en SQLite.
- Linear y Notion: SQLite en cliente via WASM.
Patrón común: un nodo aplicación + SQLite local + Litestream offsite.
Cuándo SQLite gana vs Postgres
Casos donde SQLite es mejor elección:
- App con 1 proceso (single instance VM, Lambda monolítico).
- Queries simples a moderadas, no cross-server joins.
- Volumen de datos hasta ~100GB cómodamente, ~1TB con disciplina.
- Latencia cero a la BD (está en el mismo proceso).
- Despliegue simple (no BD server separado).
- Backup como cp del archivo.
Cuándo SQLite NO basta
Honestidad:
- Múltiples procesos escribiendo: serialización de writes es bottleneck.
- Múltiples instancias de app: no puedes shard SQLite entre servers sin complejidad.
- Queries analíticos masivos: usa DuckDB (SQLite es OLTP, DuckDB es OLAP).
- Extensiones Postgres-specific (pgvector serio, PostGIS, etc).
- Roles/permisos a nivel BD: SQLite no tiene.
Concurrencia de escritura
Para write-heavy apps, SQLite tiene un write global lock. Strategies:
- Batching writes: transacciones pequeñas agrupadas.
- BEGIN IMMEDIATE para upgrade a write lock antes de retry-dolor.
- Cola en aplicación: un único escritor que procesa requests.
- Segregación: datos write-heavy en BD separada del read-heavy.
Para ~1000 writes/s sostenidos, SQLite maneja. >10k writes/s sostenidos, considera Postgres.
Migraciones
Usar sqlc (Go), SQLx (Rust), better-sqlite3 (Node). Tooling comparable a Postgres.
Para migraciones de schema:
- Goose, golang-migrate: funcionan.
- atlas: moderno, multi-BD.
Diferencias de SQL específicas (tipos, ALTER TABLE limitations) exigen care.
Extensiones útiles
- sqlite-vss: búsqueda vectorial.
- sqlite-http: llamadas HTTP desde SQL.
- sqlean: library de extensiones populares.
- SpatiaLite: geoespacial.
Crecimiento del ecosistema reciente es fuerte.
Encriptación
- SQLite Encryption Extension (SEE): de pago, oficial.
- SQLCipher: open-source, ampliamente usada.
- sqlite3mc: multiple-ciphers.
Para datos sensibles at-rest, estas herramientas son maduras.
Conclusión
SQLite es una opción seria para aplicaciones productivas 1-instance. Con WAL mode, pragmas optimizados, Litestream, y opcionalmente LiteFS o rqlite para HA, cubre casos que muchos equipos asumen que requieren PostgreSQL. La ventaja operativa es enorme: sin BD server, sin red, sin permisos, sin backup complejo. Para cargas write-heavy masivas o multi-instance-writing, Postgres sigue siendo correcto. Pero para la enorme mayoría de apps pequeñas-medianas, empezar con SQLite y crecer hacia Postgres solo si realmente hace falta es estrategia pragmática.
Síguenos en jacar.es para más sobre SQLite, bases de datos y arquitecturas simples.